package com.firstresponder.location;

import com.firstresponder.location.InferenceManager;
import com.firstresponder.db.FirstResponderDB;
import com.firstresponder.util.MessageSender;
import com.firstresponder.util.SmsReceiver;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteException;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.widget.TextView;

import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

public class LocationService extends Service {

	private static final String TAG = "FRLocationService";
	public static final int ALERT_MODE = 2;
	public static final int PASSIVE_MODE = 1;
	// TODO: Service Status needs to be started with a certain behavior.
	private static int SERVICE_STATUS = PASSIVE_MODE;
	private static final long INITIAL_TRACKING_PERIOD = 600000; // 10 minutes
	private static final long INITIAL_PERIOD_BETW_CHECKS = 60000; // 1 minute
	private static final long ALERT_TIME = 60000;// 1 minute
	private static final long PASSIVE_TIME = 300000;// 1 min; 3600000 for 1 hr
	private static boolean isServiceStarted = false;
	// private boolean locationAvailable = true;
	private boolean updateFirstRun = true;
	private boolean timerRunning = false;
	private boolean inferenceFirstRun = true;
	private boolean inferenceRunning = false;
	private final IBinder mBinder = new LocalBinder();
	// private static LocationScreen MAIN_ACTIVITY = null;
	private static MessageSender MESSAGE_SENDER = MessageSender.getInstance();
	private FirstResponderDB db;
	private LocationManager locationManager;
	private InferenceManager infManag = new InferenceManager();
	private Timer timer;
	private TimerTask updateTask = null;
	private Handler serviceHandler = new Handler();
	private Location currGpsLocation = null, currNetworkLocation = null;
	private Location currDestination = null;
	private ArrayList<Float> gpsSpeedValues = new ArrayList<Float>();
	private List<PropertyChangeListener> listener = new ArrayList<PropertyChangeListener>();

	@Override
	public void onCreate() {
		Log.i(TAG, "onCreate() Executed");
		super.onCreate();
		locationManager = (LocationManager) this
				.getSystemService(Context.LOCATION_SERVICE);
		timer = new Timer();
		SmsReceiver.serLocationService(this);
		// Hard Coded Database Test/Sample:
		db = new FirstResponderDB(getBaseContext());
		try {
			com.firstresponder.db.Location testLoc = db
					.getLocationByPriority("1");
			if (testLoc == null) {
				com.firstresponder.db.Location dbLocation = new com.firstresponder.db.Location();
				dbLocation.setCurrLat(40.73809);
				dbLocation.setCurrLong(-73.79321);
				dbLocation.setDept_id("800");
				dbLocation.setLocation_key("1");
				dbLocation.setPriority(1);
				dbLocation.setShortName("default_firehouse");
				db.insertLocation(dbLocation);
			}
		} catch (SQLiteException e) {
			Log.e(TAG, Log.getStackTraceString(e));
		}
		// End Test

		Log.i(TAG, "onCreate() Finished");
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i(TAG, "onStartCommand Executed");

		isServiceStarted = true;

		// TODO: REMOVE ANY LISTENER UPDATES BEFORE TAKING ANY ACTIONS
		removeListenerUpdates();

		// automatically enable GPS/Network providers if not already activated
		enableDeviceLocation();

		if (SERVICE_STATUS == PASSIVE_MODE) {
			Log.i(TAG, "PASSIVE MODE STARTED");
			startPassiveTimer();
		} else if (SERVICE_STATUS == ALERT_MODE) {
			Log.i(TAG, "ALERT_MODE STARTED");

			try {
				locationManager.requestLocationUpdates(
						LocationManager.GPS_PROVIDER, 10000, 0, gpsListener);

				locationManager.requestLocationUpdates(
						LocationManager.NETWORK_PROVIDER, 10000, 0,
						networkListener);
			} catch (Throwable t) {
				Log.e(TAG, Log.getStackTraceString(t));
			}
			try {
				com.firstresponder.db.Location destination = db
						.getLocationByPriority("1");
				if (destination != null) {
					setCurrDestination(destination);
				}
			} catch (SQLiteException e) {
				Log.e(TAG, Log.getStackTraceString(e));
			}
			startInference();
			updateLocation();
		}

		Log.i(TAG, "osStartCommand Finished");
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "Service Stopped");
		super.onDestroy();
		isServiceStarted = false;
		removeListenerUpdates();
	}

	private LocationListener gpsListener = new LocationListener() {

		@Override
		public void onLocationChanged(Location location) {
			Log.i(TAG, "Received Location Update from GPS_PROVIDER");
			if (currGpsLocation != null) {
				currGpsLocation = location;
			} else {
				currGpsLocation = location;
				notifyListeners();
			}
			if (location.hasSpeed()) {
				gpsSpeedValues.add(location.getSpeed());
			}
			// Sample to see what information is returning
			// MAIN_ACTIVITY.UpdateGpsText(currGpsLocation.getLatitude(),
			// gpsAccuracy, currGpsLocation.getProvider());
			Log.i(TAG, "Sent GPS Update to Server");
		}

		@Override
		public void onProviderDisabled(String provider) {
		}

		@Override
		public void onProviderEnabled(String provider) {
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
		}

	};

	private LocationListener networkListener = new LocationListener() {

		@Override
		public void onLocationChanged(Location location) {
			Log.i(TAG, "Received Location Update from NETWORK_PROVIDER");
			currNetworkLocation = location;
			// Sample to see what information is returning
			// MAIN_ACTIVITY.UpdateNetworkText(currNetworkLocation.getLatitude(),
			// networkAccuracy);
			Log.i(TAG, "Sent Network Update to Server");

		}

		@Override
		public void onProviderDisabled(String provider) {
		}

		@Override
		public void onProviderEnabled(String provider) {
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
		}

	};

	public void updateLocation() {
		updateTask = new TimerTask() {
			public void run() {
				try {
					if (updateFirstRun == false) {
						sendUpdateMessage();
						Log.i(TAG, "SENDING TEXT TO SERVER");
					} else {
						updateFirstRun = false;
						timerRunning = true;
					}
				} catch (Throwable t) {
					Log.e(TAG, Log.getStackTraceString(t));
				}
			}
		};
		timer.schedule(updateTask, 0, ALERT_TIME);
	}

	/**
	 * Countdown timer which infers whether a person is getting closer to the
	 * given destination for the first 15 minutes of the alert, checking once
	 * every minute.
	 */
	private CountDownTimer inferenceTimer = new CountDownTimer(
			INITIAL_TRACKING_PERIOD, INITIAL_PERIOD_BETW_CHECKS) {

		public void onTick(long millisUntilFinished) {
			if (!inferenceFirstRun) {
				Log.d(TAG, "not a first run of the countdown timer");
				infManag.doInference(currGpsLocation, currDestination);
			}
			if (inferenceFirstRun) {
				Log.d(TAG, "the first run of countdown timer, waits 60 sec");
				inferenceFirstRun = false;
			}
		}

		public void onFinish() {
			inferenceFirstRun = true;
			Log.d(TAG, "Timer has stopped after " + INITIAL_TRACKING_PERIOD
					+ "minutes.");
		}
	};

	/**
	 * Cancels timer when there has been a manual YES/NO response
	 */
	public void cancelInference() {
		if (inferenceRunning == true) {
			inferenceTimer.cancel();
			inferenceRunning = false;
			Log.i(TAG, "Inference Timer has been cancelled due to manual response");
		}
	}

	/**
	 * Starts inferring the current distance to the current destination in a
	 * straight line on the timer given in the inner class
	 * 
	 */
	public void startInference() {
		try {
			if (inferenceRunning == false) {
				Log.i(TAG, "Starting Inference Timer");
				inferenceTimer.start();
				inferenceRunning = true;
			}
		} catch (Throwable t) {
			Log.e(TAG, Log.getStackTraceString(t));
		}
	}

	public void startPassiveTimer() {
		timerRunning = true;
		updateTask = new TimerTask() {
			public void run() {
				serviceHandler.post(new Runnable() {
					@Override
					public void run() {
						Log.i(TAG, "Passive Timer Run");
						try {
							locationManager.requestLocationUpdates(
									LocationManager.GPS_PROVIDER, 0, 0,
									gpsListener);

							locationManager.requestLocationUpdates(
									LocationManager.NETWORK_PROVIDER, 0, 0,
									networkListener);

							new CountDownTimer(20000, 20000) {

								public void onTick(long millisUntilFinished) {
								}

								public void onFinish() {
									Log.i(TAG, "Sending Passive Timer Message");
									locationManager.removeUpdates(gpsListener);
									locationManager
											.removeUpdates(networkListener);
									sendUpdateMessage();
								}
							}.start();
						} catch (Throwable t) {
							Log.e(TAG, Log.getStackTraceString(t));
						}
					}
				});
			}
		};
		timer.schedule(updateTask, 0, PASSIVE_TIME);
	}

	private void sendUpdateMessage() {
		// TODO: REVISE THIS LOGIC!!! Feels inefficient

		if (currGpsLocation != null) {
			long gpsTimeDelta = System.currentTimeMillis()
					- currGpsLocation.getTime();
			boolean isGpsOld = gpsTimeDelta > 30000;
			float averageSpeed = 0;

			if (!isGpsOld) {
				// Send GPS Message
				MESSAGE_SENDER.sendLocUpdateMessage(
						currGpsLocation.getLatitude(),
						currGpsLocation.getLongitude(), "GPS");
			} else {
				if (!gpsSpeedValues.isEmpty()) {
					for (Float speedValue : gpsSpeedValues) {
						averageSpeed += speedValue;
					}
					averageSpeed /= gpsSpeedValues.size();
				}

				if (currNetworkLocation != null) {
					if ((gpsTimeDelta / 1000) * averageSpeed >= currNetworkLocation
							.getAccuracy() + 200) {
						// Send Network Message
						MESSAGE_SENDER.sendLocUpdateMessage(
								currNetworkLocation.getLatitude(),
								currNetworkLocation.getLongitude(), "NET");
					}
				} else {
					// Send GPS Message
					MESSAGE_SENDER.sendLocUpdateMessage(
							currGpsLocation.getLatitude(),
							currGpsLocation.getLongitude(), "GPS");
				}
			}
		} else if (currNetworkLocation != null) {
			// Send Network Message
			MESSAGE_SENDER.sendLocUpdateMessage(
					currNetworkLocation.getLatitude(),
					currNetworkLocation.getLongitude(), "NET");
		} else {
			MESSAGE_SENDER.sendUnableToLocate();
		}
	}
	
	public String reverseGeoCode(Location location) {
		List<Address> addresses;
		String geoAddress = "NO AVAILABLE ADDRESS";
		try {
			Geocoder mGC = new Geocoder(this, Locale.ENGLISH);
			addresses = mGC.getFromLocation(location.getLatitude(),
					location.getLongitude(), 1);
			if (addresses == null) {
				Log.i(TAG + "reverseGeoCode()", "Address is null");
			}
			if (addresses != null) {
				try {
					Log.i(TAG + "reverseGeoCode()", "Address IS NOT null");
					Address currentAddr = addresses.get(0);

					StringBuilder mSB = new StringBuilder("Address: \n");
					for (int i = 0; i < currentAddr.getMaxAddressLineIndex(); i++) {
						mSB.append(currentAddr.getAddressLine(i)).append(" \n");
					}
					geoAddress = mSB.toString();
					return geoAddress;

				} catch (Exception e) {
					Log.e(TAG + " error from printAddress", e.getMessage());
				}
			}
		} catch (IOException e)

		{
			Log.e(TAG + " input error from printAddress", e.getMessage());
		}
		return geoAddress;

	}

	private void enableDeviceLocation() {
		/*
		 * WARNING!!!: There is no legal way to automatically enable GPS/Network
		 * Providers in a third party application. This code simulates a button
		 * press in the Location & Security Settings category to activate the
		 * Device's Location Providers. This code should be changed in the
		 * future if a more effective way is discovered. Alternatively the user
		 * could possibly be prompted to activate his GPS/Network each time in
		 * the case its not already activated.
		 */
		String provider = Settings.Secure.getString(getContentResolver(),
				Settings.Secure.LOCATION_PROVIDERS_ALLOWED);

		final Intent touch = new Intent();
		touch.setClassName("com.android.settings",
				"com.android.settings.widget.SettingsAppWidgetProvider");
		touch.addCategory(Intent.CATEGORY_ALTERNATIVE);

		if (!provider.contains("gps")) {
			touch.setData(Uri.parse("3"));
			sendBroadcast(touch);
		}
	}

	public void removeListenerUpdates() {
		if (gpsListener != null) {
			Log.i(TAG, "REMOVED GPS LISTENER");
			locationManager.removeUpdates(gpsListener);
		}
		if (networkListener != null) {
			Log.i(TAG, "REMOVED NETWORK LISTENER");
			locationManager.removeUpdates(networkListener);
		}
		if (timerRunning == true) {
			Log.i(TAG, "REMOVING TIMER LISTENERS");
			timer.cancel();
			timer.purge();
			timer = new Timer();
		}
	}

	public static int getServiceStatus() {
		return SERVICE_STATUS;
	}

	public static void setServiceStatus(int status) {
		SERVICE_STATUS = status;
	}

	public static boolean isServiceStarted() {
		return isServiceStarted;
	}

	public Location getCurrGpsLocation() {
		return currGpsLocation;
	}

	public Location getCurrDestination() {
		return currDestination;
	}

	public void setCurrDestination(Location currDestination) {
		this.currDestination = currDestination;
		notifyListeners();
		if(inferenceRunning==true){
			cancelInference();
			startInference();
		}
	}

	public void setCurrDestination(com.firstresponder.db.Location dbDestination) {
		currDestination = new Location(LocationManager.GPS_PROVIDER);
		currDestination.setLatitude(dbDestination.getCurrLat());
		currDestination.setLongitude(dbDestination.getCurrLong());
		notifyListeners();
	}

	// public static void setMainActivity(LocationScreen activity) {
	// MAIN_ACTIVITY = activity;
	// }

	private void notifyListeners() {
		for (Iterator<PropertyChangeListener> iterator = listener.iterator(); iterator
				.hasNext();) {
			PropertyChangeListener name = (PropertyChangeListener) iterator
					.next();
			name.propertyChange(null);
		}
	}

	public void addChangeListener(PropertyChangeListener newListener) {
		listener.add(newListener);
	}

	public void removeChangeListener(PropertyChangeListener Listener) {
		listener.remove(Listener);
	}

	public class LocalBinder extends Binder {
		public LocationService getService() {
			// Returns this instance of Location Service to the Activity
			return LocationService.this;
		}
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return mBinder;
	}

}
